package org.bookshare.validator.rule;

import java.util.ArrayList;
import java.util.Iterator;

import org.apache.commons.lang.math.NumberUtils;
import org.benetech.beans.error.ValidationError.Severity;
import org.benetech.event.EventListener;
import org.bookshare.document.beans.HasXML;
import org.bookshare.validator.error.MissingElementValidationError;
import org.bookshare.validator.error.PageNumberValidationError;
import org.bookshare.validator.report.ValidationMetadata;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.filter.ElementFilter;

/**
 * Verifies certain basic page number related assertions. E.g. pages are in
 * order, pages are not missing, all pages referred to in the TOC exist.
 */
public final class XMLPageNumbersRule implements Rule {

	/**
	 * Default constructor.
	 */
	public XMLPageNumbersRule() {
		// empty
	}

    /**
     * {@inheritDoc}
     */
    public void evaluate(final ValidationMetadata validationMetadata, final EventListener listener) {

        final Document doc = ((HasXML) validationMetadata.getDocumentSet()).getXMLDocument();

        listener.message("Checking " + doc.getBaseURI() + " to verify "
                + "that specified page numbers look correct");
        final Element root = doc.getRootElement();
        final Element book = root.getChild("book", root.getNamespace());
        final Iterator<Element> pagenums =
        		(Iterator<Element>) book.getDescendants(new ElementFilter("pagenum"));
        final ArrayList<Element> pageNumArray = new ArrayList<Element>();
        while (pagenums.hasNext()) {
            pageNumArray.add(pagenums.next());
        }
        analyze(pageNumArray, doc, validationMetadata);
    }

    /**
     * Analyze the list of page number containing elementss for various patterns
     * that might indicate errors.
     * @param pagenums List of elements containing page numbers
     * @param xmlDoc The xml document parent
     * @param validationMetadata The reporting object
     */
    private void analyze(final ArrayList<Element> pagenums,
            final Document xmlDoc,
            final ValidationMetadata validationMetadata)
    {
        if (pagenums.size() == 0) {
            validationMetadata.getDocumentViolations().put(xmlDoc,
                    new MissingElementValidationError("pagenum", "root", xmlDoc.getBaseURI(),
                    		Severity.WARNING));
        } else {
            boolean inNumbers = false;
            boolean nonNumberPostNumber = false;
            Integer lastNumber = null;
            for (int i = 0; i < pagenums.size(); i++) {
            	final Element pageNumElement = pagenums.get(i);
            	final String pagenum = pageNumElement.getTextNormalize();
                final String prev = ((i - 1 >= 0) ? pagenums.get(i - 1).getTextNormalize() : null);
                final String next = ((i + 1 < pagenums.size()) ? pagenums.get(i + 1).getTextNormalize() : null);

                // if the page number is numeric, compare it to the previous
                if (NumberUtils.isNumber(pagenum)) {
                    // this is the first time we've encountered a number
                    if (!inNumbers) {
                        inNumbers = true;
                        lastNumber = Integer.parseInt(pagenum);
                    } else {
                    	final Integer number = Integer.parseInt(pagenum);
                        if (number < lastNumber) {
                            validationMetadata.getElementViolations().put(
                                pageNumElement,
                                new PageNumberValidationError(pagenum, prev, next,
                                		"The page number decreases", Severity.WARNING));
                        } else if (number.equals(lastNumber)) {
                            validationMetadata.getElementViolations().put(
                                pageNumElement,
                                new PageNumberValidationError(pagenum, prev, next,
                                        "The page number doesn't increase", Severity.WARNING));
                        } else if (number > (lastNumber + 1)) {
                            validationMetadata.getElementViolations().put(pageNumElement,
                                new PageNumberValidationError(pagenum, prev, next,
                                   "The page number increases by more than one", Severity.WARNING));
                        }
                        lastNumber = number;

                        if (nonNumberPostNumber) {
                        	validationMetadata.getElementViolations().put(pageNumElement,
                                    new PageNumberValidationError(pagenum, prev, next, "Non-number encountered "
                                     + "where a number was expected", Severity.WARNING));
                        }
                    }

                } else {
                	if (pagenum == prev) {
                		validationMetadata.getElementViolations().put(
                                pageNumElement,
                                new PageNumberValidationError(pagenum, prev, next,
                                		"The page number does not change", Severity.WARNING));
                	}

                //Flag that a non-number is found in case another number is found.

                	if (inNumbers) {
                		nonNumberPostNumber = true;

                	}
                }
            }
        }
    }

}
